﻿namespace Smark.Core
{
    using System;
    using System.Collections.Generic;
    using System.Runtime.CompilerServices;
    using System.Threading;

    public class ObjectEnter : IDisposable
    {
        private bool mDisposed;
        private string mID;
        private static Dictionary<string, LockItem> mObjectTable = new Dictionary<string, LockItem>(100);
        private string mOwnerID;

        static ObjectEnter()
        {
            Functions.Action(new AsyncDelegate(ObjectEnter.CheckExpires));
        }

        public ObjectEnter(string id)
        {
            this.mOwnerID = Guid.NewGuid().ToString("N");
            this.mID = id;
            Enter(this.mOwnerID, id, 0x2710);
        }

        public ObjectEnter(string id, int milliseconds)
        {
            this.mOwnerID = Guid.NewGuid().ToString("N");
            this.mID = id;
            Enter(this.mOwnerID, id, milliseconds);
        }

        private static void CheckExpires()
        {
            while (true)
            {
                lock (mObjectTable)
                {
                    foreach (KeyValuePair<string, LockItem> pair in mObjectTable)
                    {
                        pair.Value.Expires();
                    }
                }
                Thread.Sleep(0x1388);
            }
        }

        public void Dispose()
        {
            lock (this)
            {
                if (!this.mDisposed)
                {
                    Exit(this.mOwnerID, this.mID);
                    this.mDisposed = true;
                }
            }
        }

        public static void Enter(string owner, string id, int milliseconds)
        {
            Log.Write<ObjectEnter>(Log.LogType.Track, string.Format("Enter Resource Owner:{0}\tResource:{1}\tSeconds:{2}", owner, id, milliseconds));
            QueueState handler = new QueueState {
                EntrerMilliseconds = milliseconds
            };
            if (!GetLockItem(id).Enter(owner, milliseconds, handler))
            {
                handler.Handler.Reset();
                handler.Handler.WaitOne();
            }
        }

        public static void Exit(string owner, string id)
        {
            Log.Write<ObjectEnter>(Log.LogType.Track, string.Format("Exit Resource Owner:{0}\tResource:{1}", owner, id));
            if (mObjectTable.ContainsKey(id))
            {
                mObjectTable[id].Exit(owner);
            }
        }

        private static LockItem GetLockItem(string id)
        {
            lock (mObjectTable)
            {
                LockItem item = null;
                if (!mObjectTable.ContainsKey(id))
                {
                    item = new LockItem {
                        ID = id
                    };
                    mObjectTable.Add(id, item);
                }
                else
                {
                    item = mObjectTable[id];
                }
                return item;
            }
        }

        public static void TryEnter(string id, AsyncDelegate<string, bool> lockhandler)
        {
            TryEnter(id, 0x2710, 0x3e8, lockhandler);
        }

        public static void TryEnter(string id, int milliseconds, int timeout, AsyncDelegate<string, bool> lockhandler)
        {
            string t = Guid.NewGuid().ToString("N");
            if (lockhandler != null)
            {
                lockhandler(t, TryEnter(t, id, milliseconds, timeout));
            }
            else
            {
                TryEnter(t, id, milliseconds, timeout);
            }
        }

        public static bool TryEnter(string owner, string id, int milliseconds, int timeout)
        {
            Log.Write<ObjectEnter>(Log.LogType.Track, string.Format("TryEnter Resource Owner:{0}\tResource:{1}\tSeconds:{2}\tTimeOut:{3}", new object[] { owner, id, milliseconds, timeout }));
            QueueState handler = new QueueState {
                EntrerMilliseconds = milliseconds,
                TimeOut = timeout
            };
            if (!GetLockItem(id).Enter(owner, milliseconds, handler))
            {
                handler.Handler.Reset();
                handler.Handler.WaitOne(handler.TimeOut);
                return GetLockItem(id).Enter(owner, milliseconds, null);
            }
            return true;
        }

        private class LockItem
        {
            private Queue<QueueState> mWaitHandle = new Queue<QueueState>();
            private const string SYSTEM_ID = "SYSTEM_4EB49CA1-7470-44af-94DE-B2128B9B5BA9";

            public bool Enter(string owner, int milliseconds, QueueState handler)
            {
                lock (this)
                {
                    if ((this.Count > 0) && (owner != this.Owner))
                    {
                        if (handler != null)
                        {
                            this.EnterWait(handler);
                        }
                        return false;
                    }
                    this.Count++;
                    if (milliseconds > 0x1d4c0)
                    {
                        milliseconds = 0x1d4c0;
                    }
                    this.EntrerMilliseconds = milliseconds;
                    this.Owner = owner;
                    this.EnterTime = DateTime.Now;
                    return true;
                }
            }

            private void EnterWait(QueueState handler)
            {
                lock (this.mWaitHandle)
                {
                    this.mWaitHandle.Enqueue(handler);
                }
            }

            public void Exit(string owner)
            {
                lock (this)
                {
                    if (owner == "SYSTEM_4EB49CA1-7470-44af-94DE-B2128B9B5BA9")
                    {
                        this.Owner = null;
                        this.Count = 0;
                    }
                    else if (owner == this.Owner)
                    {
                        this.Count--;
                    }
                    if (this.Count == 0)
                    {
                        lock (this.mWaitHandle)
                        {
                            if (this.mWaitHandle.Count > 0)
                            {
                                this.mWaitHandle.Dequeue().Handler.Set();
                            }
                        }
                    }
                }
            }

            public void Expires()
            {
                lock (this)
                {
                    TimeSpan span = (TimeSpan) (DateTime.Now - this.EnterTime);
                    if (span.TotalMilliseconds > this.EntrerMilliseconds)
                    {
                        this.Exit("SYSTEM_4EB49CA1-7470-44af-94DE-B2128B9B5BA9");
                    }
                }
            }

            public int Count { get; set; }

            public DateTime EnterTime { get; set; }

            public int EntrerMilliseconds { get; set; }

            public string ID { get; set; }

            public string Owner { get; set; }
        }
    }
}

